/*-------------------------------------------------------------------------
 *
 * nodeBitmapIndexscan.c
 *	  支持关系的位图索引扫描的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeBitmapIndexscan.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *		MultiExecBitmapIndexScan	使用索引扫描关系。
 *		ExecInitBitmapIndexScan		创建并初始化状态信息。
 *		ExecReScanBitmapIndexScan	准备重新扫描计划。
 *		ExecEndBitmapIndexScan		释放所有存储。
 */
#include "postgres.h"

#include "access/genam.h"
#include "executor/execdebug.h"
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeIndexscan.h"
#include "miscadmin.h"
#include "utils/memutils.h"


/* ----------------------------------------------------------------
 *		ExecBitmapIndexScan
 *
 *		合规的存根
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecBitmapIndexScan(PlanState *fc_pstate)
{
	elog(ERROR, "BitmapIndexScan node does not support ExecProcNode call convention");
	return NULL;
}

/* ----------------------------------------------------------------
 *		MultiExecBitmapIndexScan(node)
 * ----------------------------------------------------------------
 */
Node * MultiExecBitmapIndexScan(BitmapIndexScanState *fc_node)
{
	TIDBitmap  *fc_tbm;
	IndexScanDesc fc_scandesc;
	double		fc_nTuples = 0;
	bool		fc_doscan;

	/* 必须提供我们自己的仪器支持 */
	if (fc_node->ss.ps.instrument)
		InstrStartNode(fc_node->ss.ps.instrument);

	/*
	 * 从索引扫描节点提取必要信息
	 */
	fc_scandesc = fc_node->biss_ScanDesc;

	/*
	 * 如果我们有运行时键并且尚未设置，请立即设置。
	 * 数组键也被视为运行时键；请注意，如果ExecReScan
	 * 返回时biss_RuntimeKeysReady仍为false，则表示存在一个空
	 * 数组键，因此我们不应该做任何事情。
	 */
	if (!fc_node->biss_RuntimeKeysReady &&
		(fc_node->biss_NumRuntimeKeys != 0 || fc_node->biss_NumArrayKeys != 0))
	{
		ExecReScan((PlanState *) fc_node);
		fc_doscan = fc_node->biss_RuntimeKeysReady;
	}
	else
		fc_doscan = true;

	/*
	 * 准备结果位图。通常，我们只是创建一个新位图以返回；
	 * 然而，我们的父节点被允许将一个预先制作的位图存储到
	 * node->biss_result中，在这种情况下，我们只是将元组ID
	 * 或到现有位图中。（这节省了需要显式UNION步骤的时间。）
	 */
	if (fc_node->biss_result)
	{
		fc_tbm = fc_node->biss_result;
		fc_node->biss_result = NULL;	/* 为下次重置 */
	}
	else
	{
		/* XXX 我们应该对这个使用小于work_mem吗？ */
		fc_tbm = tbm_create(work_mem * 1024L,
						 ((BitmapIndexScan *) fc_node->ss.ps.plan)->isshared ?
						 fc_node->ss.ps.state->es_query_dsa : NULL);
	}

	/*
	 * 从索引获取TID并插入位图
	 */
	while (fc_doscan)
	{
		fc_nTuples += (double) index_getbitmap(fc_scandesc, fc_tbm);

		CHECK_FOR_INTERRUPTS();

		fc_doscan = ExecIndexAdvanceArrayKeys(fc_node->biss_ArrayKeys,
										   fc_node->biss_NumArrayKeys);
		if (fc_doscan)				/* 重置索引扫描 */
			index_rescan(fc_node->biss_ScanDesc,
						 fc_node->biss_ScanKeys, fc_node->biss_NumScanKeys,
						 NULL, 0);
	}

	/* 必须提供我们自己的仪器支持 */
	if (fc_node->ss.ps.instrument)
		InstrStopNode(fc_node->ss.ps.instrument, fc_nTuples);

	return (Node *) fc_tbm;
}

/* ----------------------------------------------------------------
 *		ExecReScanBitmapIndexScan(node)
 *
 *		重新计算任何依赖于运行时已知信息的扫描键的值，
 *		然后重新扫描已索引的关系。
 * ----------------------------------------------------------------
 */
void ExecReScanBitmapIndexScan(BitmapIndexScanState *fc_node)
{
	ExprContext *fc_econtext = fc_node->biss_RuntimeContext;

	/*
	 * 重置运行时键上下文，以便在扫描每个外部元组时不泄漏内存。
	 * 请注意，这假设我们将在每次调用时重新计算*所有*运行时键。
	 */
	if (fc_econtext)
		ResetExprContext(fc_econtext);

	/*
	 * 如果我们正在进行运行时键计算（即，任何索引键值不
	 * 是简单常量），则计算新的键值。
	 *
	 * 数组键也被视为运行时键；请注意，如果我们返回
	 * 时biss_RuntimeKeysReady仍为false，则存在一个空数组
	 * 键，因此不需要进行索引扫描。
	 */
	if (fc_node->biss_NumRuntimeKeys != 0)
		ExecIndexEvalRuntimeKeys(fc_econtext,
								 fc_node->biss_RuntimeKeys,
								 fc_node->biss_NumRuntimeKeys);
	if (fc_node->biss_NumArrayKeys != 0)
		fc_node->biss_RuntimeKeysReady =
			ExecIndexEvalArrayKeys(fc_econtext,
								   fc_node->biss_ArrayKeys,
								   fc_node->biss_NumArrayKeys);
	else
		fc_node->biss_RuntimeKeysReady = true;

	/* 重置索引扫描 */
	if (fc_node->biss_RuntimeKeysReady)
		index_rescan(fc_node->biss_ScanDesc,
					 fc_node->biss_ScanKeys, fc_node->biss_NumScanKeys,
					 NULL, 0);
}

/* ----------------------------------------------------------------
 *		ExecEndBitmapIndexScan
 * ----------------------------------------------------------------
 */
void ExecEndBitmapIndexScan(BitmapIndexScanState *fc_node)
{
	Relation	fc_indexRelationDesc;
	IndexScanDesc fc_indexScanDesc;

	/*
	 * 从节点中提取信息
	 */
	fc_indexRelationDesc = fc_node->biss_RelationDesc;
	fc_indexScanDesc = fc_node->biss_ScanDesc;

	/*
	 * 释放exprcontext ... 现在已作废，见ExecFreeExprContext
	 */
#ifdef NOT_USED
	if (fc_node->biss_RuntimeContext)
		FreeExprContext(fc_node->biss_RuntimeContext, true);
#endif

	/*
	 * 关闭索引关系（如果我们没有打开它，则无操作）
	 */
	if (fc_indexScanDesc)
		index_endscan(fc_indexScanDesc);
	if (fc_indexRelationDesc)
		index_close(fc_indexRelationDesc, NoLock);
}

/* ----------------------------------------------------------------
 *		ExecInitBitmapIndexScan
 *
 *		初始化索引扫描的状态信息。
 * ----------------------------------------------------------------
 */
BitmapIndexScanState *
ExecInitBitmapIndexScan(BitmapIndexScan *fc_node, EState *fc_estate, int fc_eflags)
{
	BitmapIndexScanState *fc_indexstate;
	LOCKMODE	fc_lockmode;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	/*
	 * 创建状态结构
	 */
	fc_indexstate = makeNode(BitmapIndexScanState);
	fc_indexstate->ss.ps.plan = (Plan *) fc_node;
	fc_indexstate->ss.ps.state = fc_estate;
	fc_indexstate->ss.ps.ExecProcNode = fc_ExecBitmapIndexScan;

	/* 通常我们在运行时不会生成结果位图 */
	fc_indexstate->biss_result = NULL;

	/*
	 * 我们在这里不打开或锁定基本关系。我们假设一个
	 * 祖先BitmapHeapScan节点在执行计划树的整个过程中
	 * 持有对堆关系的AccessShareLock（或更好的锁）。
	 */

	fc_indexstate->ss.ss_currentRelation = NULL;
	fc_indexstate->ss.ss_currentScanDesc = NULL;

	/*
	 * 杂项初始化
	 *
	 * 我们不需要这个节点的标准 exprcontext，尽管我们可能会在下面决定创建一个运行时键 exprcontext
	 */

	/*
	 * 初始化子表达式
	 *
	 * 我们不需要初始化目标列表或条件，因为都没有使用。
	 *
	 * 注意：我们不初始化所有的 indexqual 表达式，仅初始化与运行时键对应的子部分（见下文）。
	 */

	/*
	 * 如果我们只是执行 EXPLAIN（即，不打算运行计划），则在这里停止。
	 * 这允许索引建议插件 EXPLAIN 一个包含对不存在的索引引用的计划。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return fc_indexstate;

	/* 打开索引关系。 */
	fc_lockmode = exec_rt_fetch(fc_node->scan.scanrelid, fc_estate)->rellockmode;
	fc_indexstate->biss_RelationDesc = index_open(fc_node->indexid, fc_lockmode);

	/*
	 * 初始化特定于索引的扫描状态
	 */
	fc_indexstate->biss_RuntimeKeysReady = false;
	fc_indexstate->biss_RuntimeKeys = NULL;
	fc_indexstate->biss_NumRuntimeKeys = 0;

	/*
	 * 从索引条件构建索引扫描键
	 */
	ExecIndexBuildScanKeys((PlanState *) fc_indexstate,
						   fc_indexstate->biss_RelationDesc,
						   fc_node->indexqual,
						   false,
						   &fc_indexstate->biss_ScanKeys,
						   &fc_indexstate->biss_NumScanKeys,
						   &fc_indexstate->biss_RuntimeKeys,
						   &fc_indexstate->biss_NumRuntimeKeys,
						   &fc_indexstate->biss_ArrayKeys,
						   &fc_indexstate->biss_NumArrayKeys);

	/*
	 * 如果我们有运行时键或数组键，我们需要一个 ExprContext 来
	 * 评估它们。我们可以创建一个“标准”的计划节点 exprcontext，
	 * 但为了保持与 nodeIndexscan.c 的代码相似，似乎更好坚持使用
	 * 使用单独的 ExprContext 的方法。
	 */
	if (fc_indexstate->biss_NumRuntimeKeys != 0 ||
		fc_indexstate->biss_NumArrayKeys != 0)
	{
		ExprContext *fc_stdecontext = fc_indexstate->ss.ps.ps_ExprContext;

		ExecAssignExprContext(fc_estate, &fc_indexstate->ss.ps);
		fc_indexstate->biss_RuntimeContext = fc_indexstate->ss.ps.ps_ExprContext;
		fc_indexstate->ss.ps.ps_ExprContext = fc_stdecontext;
	}
	else
	{
		fc_indexstate->biss_RuntimeContext = NULL;
	}

	/*
	 * 初始化扫描描述符。
	 */
	fc_indexstate->biss_ScanDesc =
		index_beginscan_bitmap(fc_indexstate->biss_RelationDesc,
							   fc_estate->es_snapshot,
							   fc_indexstate->biss_NumScanKeys);

	/*
	 * 如果没有运行时键需要计算，则继续将扫描键传递给
	 * 索引 AM。
	 */
	if (fc_indexstate->biss_NumRuntimeKeys == 0 &&
		fc_indexstate->biss_NumArrayKeys == 0)
		index_rescan(fc_indexstate->biss_ScanDesc,
					 fc_indexstate->biss_ScanKeys, fc_indexstate->biss_NumScanKeys,
					 NULL, 0);

	/*
	 * 完成所有操作。
	 */
	return fc_indexstate;
}
